** 遇到發文存檔失敗,所以分兩部分發文,控制檔案SysScheduleCtlEntity 以及排程執行紀錄檔案SysScheduleLogEntity。請參考(5-1) - Spring Boot 排程作業(Schedule) URL: https://ithelp.ithome.com.tw/articles/10398729
這裡準備提供Schedule Job 功能設定,基礎控制與紀錄功能等架構,方便後續增加任何排程,只需要簡單增加一個Worker繼承基本BaseScheduleTask,實作triggerScheduleTask 設定排程時間週期規則,以及doTaskWorker實際排程需要執行的功能。
** 主要提供功能:
package tw.lewishome.webapp.base.schedule;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.FixedDelayTask;
import org.springframework.scheduling.config.FixedRateTask;
import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import tw.lewishome.webapp.base.utility.common.CommUtils;
import tw.lewishome.webapp.base.utility.common.NetUtils;
import tw.lewishome.webapp.database.primary.entity.SysScheduleCtlEntity;
import tw.lewishome.webapp.database.primary.entity.SysScheduleLogEntity;
import tw.lewishome.webapp.database.primary.repository.SysScheduleCtlRepository;
import tw.lewishome.webapp.database.primary.repository.SysScheduleLogRepository;
/**
* 排程任務服務類別,負責管理排程任務的執行狀態、控制資料及日誌記錄。
*
* 此服務提供以下功能:
* <ul>
* <li>判斷指定排程任務是否正在執行中,並根據控制資料與 Lock 資料進行判斷。</li>
* <li>首次執行時自動新增控制資料與 Lock 資料,確保主機啟動排程任務。</li>
* <li>根據控制資料設定,判斷是否允許執行排程任務,並檢查主機名稱是否符合。</li>
* <li>新增與結束排程任務執行日誌,方便追蹤任務執行狀態。</li>
* <li>新增與刪除排程任務 Lock 資料,確保同一時間僅有一臺主機執行任務,避免多主機衝突。</li>
* </ul>
*
* <b>注意事項:</b>
* <ul>
* <li>多臺主機同時啟動排程任務時,可能會因 Lock 資料未及時刪除而產生衝突,建議排程任務執行結束時務必刪除 Lock 資料。</li>
* <li>控制資料中的 RunFlag 與 ServerName 設定將影響排程任務是否允許執行及執行主機。</li>
* <li>所有資料操作皆透過 Repository 進行,確保資料一致性。</li>
* </ul>
*
* @author Lewis
* @since 2024
*/
@Service
@Slf4j
public class ScheduleTaskService {
/**
* Fix for javadoc warning :
* use of default constructor, which does not provide a comment
* Constructs a new ScheduleTaskService instance.
* This is the default constructor, implicitly provided by the compiler
* if no other constructors are defined.
*/
public ScheduleTaskService() {
// Constructor body (can be empty)
}
@Autowired
ApplicationContext applicationContext;
@Autowired
SysScheduleLogRepository sysScheduleLogRepository;
@Autowired
SysScheduleCtlRepository sysScheduleCtlRepository;
/**
* 判斷目前主機是否為排程Master主機 (masterServer)
*
* 此方法會檢查 SysScheduleCtl 資料表中的 Main_Server 記錄,
* 若該記錄的 ServerName 與目前主機名稱相符,則視為排程主機。
* 若該記錄不存在,則會新增一筆記錄,並將目前主機名稱設為排程主機。
*
* @param serverName 目前主機名稱
* @return 若為排程主機則回傳 true,否則回傳 false
*/
public Boolean isScheduleMainServer(String serverName) {
String mainServerName = NetUtils.getHostName().trim();
SysScheduleCtlEntity sysScheduleCtlEntityMainServer = new SysScheduleCtlEntity();
SysScheduleCtlEntity.DataKey dataKeyMainServer = new SysScheduleCtlEntity.DataKey();
dataKeyMainServer.setScheduleTaskName("Main_Server");
dataKeyMainServer.setDataType("masterServer");
sysScheduleCtlEntityMainServer = sysScheduleCtlRepository.findById(dataKeyMainServer).orElse(null);
// 取得 masterServer 名稱
if (sysScheduleCtlEntityMainServer != null) {
mainServerName = sysScheduleCtlEntityMainServer.getServerName().trim();
// 比對是否為 masterServer
if (StringUtils.equalsIgnoreCase(mainServerName, serverName)) {
return true;
}
} else { // 沒有設定 masterServer,新增一筆資料
sysScheduleCtlEntityMainServer = new SysScheduleCtlEntity();
SysScheduleCtlEntity.DataKey dataKeyMainServerNew = new SysScheduleCtlEntity.DataKey();
dataKeyMainServerNew.setScheduleTaskName("Main_Server");
dataKeyMainServerNew.setDataType("masterServer");
sysScheduleCtlEntityMainServer.setDataKey(dataKeyMainServerNew);
sysScheduleCtlEntityMainServer.setServerName(serverName);
sysScheduleCtlRepository.saveAndFlush(sysScheduleCtlEntityMainServer);
return true;
}
// 不是 masterServer
return false;
}
/**
* 判斷指定的排程任務是否正在執行中。
*
* 此方法會根據排程任務名稱,檢查控制資料與 Lock 資料,判斷任務是否執行中。
* 若為首次執行,會主要主機(Main Server)自動新增控制資料
* 若控制資料主機發現沒有 Lock 資料自動新增 (停機時未處理好) 。
* 若控制資料設定不執行或指定主機名稱與目前主機名稱不符,則視為正在執行中。
*
*
* @param scheduleTaskName 排程任務名稱
* @return 若排程任務正在執行或不允許執行,則回傳 true 執行中(不允許);否則回傳 false 可執行
*/
public Boolean isScheduleTaskRunning(String scheduleTaskName) {
String currentServer = NetUtils.getHostName().trim();
// 取得 sysSchedule control 控制資料
SysScheduleCtlEntity.DataKey dataKeyCtl = new SysScheduleCtlEntity.DataKey();
dataKeyCtl.setScheduleTaskName(scheduleTaskName);
dataKeyCtl.setDataType("controller");
SysScheduleCtlEntity sysScheduleCtlEntity = sysScheduleCtlRepository.findById(dataKeyCtl).orElse(null);
// 沒有 sysSchedule control 控制資料,代表第一次執行,需要新增一筆 控制資料以及 Lock資料 (認定 master server
// 主機啟動執行)
if (sysScheduleCtlEntity == null) {
log.info(scheduleTaskName + " First time trigger {} sysScheduleCtl has no record)", scheduleTaskName);
// 新增 scheduleTaskName controller 資料
sysScheduleCtlEntity = new SysScheduleCtlEntity();
dataKeyCtl = new SysScheduleCtlEntity.DataKey();
dataKeyCtl.setScheduleTaskName(scheduleTaskName);
dataKeyCtl.setDataType("controller");
sysScheduleCtlEntity.setDataKey(dataKeyCtl);
sysScheduleCtlEntity.setServerName(currentServer);
sysScheduleCtlRepository.saveAndFlush(sysScheduleCtlEntity);
addSysScheduleLog(scheduleTaskName);
// 第一次新增 Controller ,暫不執行,等待下次執行時刪除 Lock 資料 。
return true;
}
String sysScheduleCtlServer = sysScheduleCtlEntity.getServerName().trim();
if (sysScheduleCtlEntity.getRunFlag() != null && Boolean.FALSE.equals(sysScheduleCtlEntity.getRunFlag())) {
// Schedule RunFlag設定不執行。
log.info(scheduleTaskName + " sysScheduleCtl running flag is false (not Running)");
// 回傳 執行中
return true;
}
// sysScheduleCtl 沒有指定主機(空白), 可以多臺主機執行,刪除Lock確認可否執行 。
if (StringUtils.isBlank(sysScheduleCtlServer)) {
// 刪除Lock失敗,表示 執行中
if (Boolean.FALSE.equals(delSysScheduleLock(scheduleTaskName))) {
log.info(scheduleTaskName + " Do delete lock Failed (schedule should be running)");
return true;
}
// 刪除 Lock 成功,表示沒有執行中
return false;
} else { // sysScheduleCtl 指定主機(非空白), 比對主機名稱是否相符
if (StringUtils.equalsIgnoreCase(sysScheduleCtlServer, currentServer)) {
log.info(currentServer + " is " + scheduleTaskName + " Register server ");
// 是指定主機,不會重複執行(就算執行時間超過週期), 所以一律刪除lock (不管成功否)
delSysScheduleLock(scheduleTaskName);
// 回傳 沒有執行中。
return false;
}
}
return true;
}
/**
* 新增 SysScheduleLog
*
* @param scheduleTaskName task Name
* @return String SysScheduleLog Uuid
*/
public String addSysScheduleLog(String scheduleTaskName) {
SysScheduleLogEntity oneSysScheduleLogEntity = new SysScheduleLogEntity();
SysScheduleLogEntity.DataKey dataKey = new SysScheduleLogEntity.DataKey();
oneSysScheduleLogEntity.setDataKey(dataKey);
oneSysScheduleLogEntity.setScheduleTaskName(scheduleTaskName);
oneSysScheduleLogEntity.setServerName(NetUtils.getHostName());
sysScheduleLogRepository.saveAndFlush(oneSysScheduleLogEntity);
return dataKey.getUuid();
}
/**
* 系統紀錄結束 SysScheduleLog
*
* @param scheduleLogUUid scheduleUUid
*/
public void endSysScheduleLog(String scheduleLogUUid) {
try {
SysScheduleLogEntity.DataKey dataKey = new SysScheduleLogEntity.DataKey();
dataKey.setUuid(scheduleLogUUid);
Optional<SysScheduleLogEntity> optionalSysScheduleLogEntity = sysScheduleLogRepository.findById(dataKey);
if (optionalSysScheduleLogEntity.isPresent()) {
SysScheduleLogEntity oneSysScheduleLogEntity = optionalSysScheduleLogEntity.get();
sysScheduleLogRepository.saveAndFlush(oneSysScheduleLogEntity);
}
} catch (Exception ex) {
log.error("SysScheduleLog {} not Found", scheduleLogUUid);
}
}
/**
* 新增 SysScheduleCtl Lock 資料 for Task Name
*
* @param scheduleTaskName task Name
*/
public void addSysScheduleCtlLock(String scheduleTaskName) throws RuntimeException {
SysScheduleCtlEntity sysScheduleCtlEntity = new SysScheduleCtlEntity();
SysScheduleCtlEntity.DataKey dataKey = new SysScheduleCtlEntity.DataKey();
dataKey.setScheduleTaskName(scheduleTaskName.trim());
dataKey.setDataType("taskLock");
sysScheduleCtlEntity.setDataKey(dataKey);
sysScheduleCtlEntity.setServerName(NetUtils.getHostName());
sysScheduleCtlEntity.setRunFlag(true);
try {
sysScheduleCtlRepository.saveAndFlush(sysScheduleCtlEntity);
} catch (Exception ex) {
throw new RuntimeException(" Add " + scheduleTaskName + " Lock Record Fail");
}
}
/**
* 刪除 SysScheduleLock by Task Name (確認 Task 要執行)
*
* @return Boolean isRunning
* @param scheduleTaskName a String object
*/
public Boolean delSysScheduleLock(String scheduleTaskName) {
try {
SysScheduleCtlEntity.DataKey dataKey = new SysScheduleCtlEntity.DataKey();
dataKey.setScheduleTaskName(scheduleTaskName.trim());
dataKey.setDataType("taskLock");
sysScheduleCtlRepository.deleteById(dataKey);
} catch (Exception ex) {
return false;
}
return true;
}
/**
*
* 解除所有排程任務鎖定
*
*/
public void releaseAllScheduleTaskLocks() {
try {
List<String> listAllScheduleTasks = listScheduledTaskJobs();
for (String oneScheduleTasks : listAllScheduleTasks) {
addSysScheduleCtlLock(oneScheduleTasks.trim());
}
} catch (Exception ex) {
log.error("Failed to release all schedule task locks: " + ex.getMessage());
}
}
/**
*
* 鎖定所有排程任務
*
*/
public void lockAllScheduleTasks() {
try {
sysScheduleCtlRepository.deleteByDataType("taskLock");
log.info("Released all schedule task locks, total released ");
} catch (Exception ex) {
log.error("Failed to release all schedule task locks: " + ex.getMessage());
}
}
/**
* 列出所有已註冊的 Scheduled Task Jobs
*
* @return a List object
*/
public List<String> listScheduledTaskJobs() {
List<String> listScheduleJobs = new java.util.ArrayList<>();
ScheduledAnnotationBeanPostProcessor postProcessor = applicationContext
.getBean(ScheduledAnnotationBeanPostProcessor.class);
Set<ScheduledTask> scheduledTasks = postProcessor.getScheduledTasks() == null ? Set.of()
: postProcessor.getScheduledTasks();
log.info("--- Listing Scheduled Jobs ---");
if (scheduledTasks.isEmpty()) {
log.info("No scheduled jobs found.");
return listScheduleJobs;
}
for (ScheduledTask scheduledTask : scheduledTasks) {
Object task = scheduledTask.getTask();
String taskString = task.toString();
List<String> splitTaskString = CommUtils.splitDelimiter(taskString, ".");
if (splitTaskString.size() > 1) {
String methodName = splitTaskString.get(splitTaskString.size() - 2);
log.info(" Method: " + methodName);
listScheduleJobs.add(methodName.trim());
} else {
log.info(" Method: " + taskString);
listScheduleJobs.add(taskString.trim());
}
log.info(" Task: " + task.toString());
if (task instanceof CronTask cronTask) {
log.info(" Type: Cron");
log.info(" Cron Expression: " + cronTask.getExpression());
} else if (task instanceof FixedRateTask fixedRateTask) {
log.info(" Type: Fixed Rate");
log.info(" Fixed Rate: " + fixedRateTask.getIntervalDuration().toMillis() + " ms");
log.info(" Initial Delay: " + fixedRateTask.getIntervalDuration().toMillis() + " ms");
} else if (task instanceof FixedDelayTask fixedDelayTask) {
log.info(" Type: Fixed Delay");
log.info(" Fixed Delay: " + fixedDelayTask.getIntervalDuration().toMillis() + " ms");
log.info(" Initial Delay: " + fixedDelayTask.getIntervalDuration().toMillis() + " ms");
} else {
log.info(" Type: Unknown");
}
log.info("----------------------------");
}
return listScheduleJobs;
}
}
** 主要提供功能:
package tw.lewishome.webapp.base.schedule;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import tw.lewishome.webapp.base.utility.common.NetUtils;
/**
*
* 排程組態類別,負責設定應用程式的排程任務執行緒池與排程任務鎖定機制。
*
*
*
* 此類別於應用程式啟動時執行,根據系統參數 <code>MAIN_SERVER_NAME</code> 進行主機排程任務鎖定的初始化:
* <ul>
* <li>若未設定 <code>MAIN_SERVER_NAME</code> 。</li>
* <li>若主機為 <code>MAIN_SERVER</code>,則解除所有排程任務鎖定。</li>
* <li>否則僅顯示主機資訊。</li>
* </ul>
*
*
*
* 此類別同時建立一個具備 100 條執行緒的排程任務執行緒池,並於應用程式關閉時自動釋放資源。
*
*
*
* 主要功能:
* <ul>
* <li>設定排程任務的執行緒池。</li>
* <li>根據系統參數進行主機排程任務鎖定的初始化。</li>
* <li>確保排程任務執行緒池於關機時自動關閉。</li>
* </ul>
*
*
*
* 注意事項:
* <ul>
* <li>請確保 <code>GetSysParmValueService</code> 已正確注入,否則排程初始化可能失敗。</li>
* <li>執行緒池大小可依實際需求調整,預設為 100 條執行緒。</li>
* </ul>
*
*
* @author Lewis
* @since 2024
*/
@Configuration
@EnableScheduling
@Slf4j
public class ScheduleConfiguration implements SchedulingConfigurer {
/**
* Fix for javadoc warning :
* use of default constructor, which does not provide a comment
* Constructs a new ScheduleConfiguration instance.
* This is the default constructor, implicitly provided by the compiler
* if no other constructors are defined.
*
*/
public ScheduleConfiguration() {
// Constructor body (can be empty)
}
@Autowired
private ScheduleTaskService scheduleTaskService;
private static final String HOST_NAME = StringUtils.defaultIfBlank(NetUtils.getHostName(), "UNKNOWN_HOST");
// @Autowired
// private ScheduleTaskService scheduleTaskService;
/**
*
*
*
* 設定排程任務執行緒池並處理排程任務鎖定。
*
*
* 此方法於 Spring 啟動時自動執行,負責:
* <ul>
* <li>將排程任務註冊器的執行緒池設為自訂的 100 條執行緒池。</li>
* <li>根據系統參數 <code>MAIN_SERVER_NAME</code> 決定是否新增或解除排程任務鎖定。</li>
* </ul>
*
*
* 執行流程:
* <ul>
* <li>取得主機名稱與系統參數 <code>MAIN_SERVER_NAME</code>。</li>
* <li>若參數未設定,則新增並解除所有排程任務鎖定。</li>
* <li>若主機為 <code>MAIN_SERVER</code>,則解除所有排程任務鎖定。</li>
* <li>否則僅顯示主機資訊。</li>
* </ul>
*
*/
@Override
public void configureTasks(@NonNull ScheduledTaskRegistrar taskRegistrar) {
log.info("Schedule Configure Tasks Started");
taskRegistrar.setScheduler(taskExecutor());
// 檢查 MAIN_SERVER_NAME 是否有設定
if (scheduleTaskService.isScheduleMainServer(HOST_NAME)) {
// 是 MAIN_SERVER,解除所有排程任務鎖定
log.info("This Host [{}] is MAIN_SERVER, Release all schedule task locks.", HOST_NAME);
scheduleTaskService.lockAllScheduleTasks();
return;
} else {
// 不是 MAIN_SERVER,僅顯示主機資訊
log.info("This Host [{}] is not MAIN_SERVER, No action taken for schedule task locks.", HOST_NAME);
}
}
/**
*
* 建立排程任務執行緒池。
*
*
* 此方法會建立一個具備 5 條執行緒的排程任務執行緒池,供排程任務使用。
*
*
* @return ThreadPoolTaskScheduler 排程任務執行緒池
*/
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5); // Set the desired pool size
scheduler.setThreadNamePrefix("my-scheduled-task-"); // Prefix for thread names
scheduler.setAwaitTerminationSeconds(60); // Wait up to 60 seconds for tasks to complete on shutdown
scheduler.setWaitForTasksToCompleteOnShutdown(true); // Wait for tasks to complete
return scheduler;
}
/**
*
* 建立排程任務執行緒池,於關機時自動關閉。
*
*
* 此方法會建立一個具備 100 條執行緒的排程任務執行緒池,並於 Spring 容器關閉時自動釋放資源。
*
*
* @return Executor 排程任務執行緒池,供排程任務使用
*/
@Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
}
** 主要提供功能:
package tw.lewishome.webapp.base.schedule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 抽象基礎排程任務類別,提供排程任務執行的標準流程與相關操作方法。
*
* 此類別作為所有排程任務的基礎,統一管理排程啟動、執行狀態檢查、日誌記錄等功能,
* 並要求子類別實作具體的排程邏輯與任務內容。
*
* <ul>
* <li>自動注入
* {@link tw.lewishome.webapp.base.schedule.ScheduleTaskService},用於排程任務的狀態管理與日誌記錄。</li>
* <li>提供 {@link #doScheduleTask(Class)} 方法,依據系統參數判斷排程是否啟動,
* 並確保同一任務不重複執行,執行過程中會記錄開始與結束日誌。</li>
* <li>定義 {@link #triggerScheduleTask()} 與 {@link #doTaskWorker()} 抽象方法,
* 強制子類別根據業務需求實作排程觸發邏輯與具體任務內容。</li>
* </ul>
*
* 使用方式:<br>
* 請繼承此類別並實作 {@link #triggerScheduleTask()} 及 {@link #doTaskWorker()} 方法,
* 以定義排程任務的觸發條件、執行週期及具體執行內容。
*
*
* @author Lewis
* @since 2024
*/
@Service
public abstract class BaseScheduleTask {
/**
* Fix for javadoc warning :
* use of default constructor, which does not provide a comment
* Constructs a new BaseScheduleTask instance.
* This is the default constructor, implicitly provided by the compiler
* if no other constructors are defined.
*/
public BaseScheduleTask() {
// Constructor body (can be empty)
}
@Autowired
ScheduleTaskService scheduleTaskService;
// @Autowired
// GetSysParmValueService GetSysParmValueService;
/**
* 執行排程任務的主要方法。
*
* 此方法會依據系統參數判斷排程是否啟動,並檢查任務是否正在執行,
* 若未執行則開始執行任務並記錄執行日誌。
*
*
* @param taskClass 排程任務的類別,用於取得任務名稱
*/
@SuppressWarnings("rawtypes")
public void doScheduleTask(Class taskClass) {
String scheduleTaskName = taskClass.getSimpleName();
// 檢查 Schedule Task是否執行中,(多主機,或 Task Over flow run Times)
// checkScheduleRunning 回傳 true,表示正在執行相同作業,
if (Boolean.FALSE.equals(scheduleTaskService.isScheduleTaskRunning(scheduleTaskName))) {
// System.out.println(scheduleTaskName + " is Start");
String scheduleUUid = scheduleTaskService.addSysScheduleLog(scheduleTaskName);
doTaskWorker();
scheduleTaskService.endSysScheduleLog(scheduleUUid);
scheduleTaskService.addSysScheduleCtlLock(scheduleTaskName);
// System.out.println(scheduleTaskName + " is End");
}
}
/**
* 指定實作 triggerScheduleTask 方法,定義排程任務的觸發邏輯與執行週期。
*
* 子類別需根據業務需求實作此方法,設定排程觸發條件與週期。
*
*/
public abstract void triggerScheduleTask();
/**
* 指定實作 doTaskWorker 執行排程任務的工作方法。
*
* 子類別需實作此方法以定義具體的排程任務內容。
*
*/
public abstract void doTaskWorker();
}
** 新增 ScheduleTaskWorkerNoEntityManagerSample 範例 於 worker Package
主要展示新增排程任務,
** 以下程式內註解,誤認為垃圾廣告。參照ScheduleTaskWorkerPrimarySample,請自行加入 worker 的 triggerScheduleTasks 的註解
package tw.lewishome.webapp.base.schedule.worker;
import java.util.concurrent.TimeUnit;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import tw.lewishome.webapp.base.schedule.BaseScheduleTask;
/**
*
* ScheduleTaskWorkerNoEntityManagerSample 是一個排程任務範例,繼承自
* {@link tw.lewishome.webapp.base.schedule.BaseScheduleTask},
* 主要展示如何在不使用 EntityManager 的情境下執行排程任務。
*
*
*
* 本類別包含多種排程註解(@Scheduled)的設定範例,說明如何透過 fixedDelay、fixedRate、initialDelay 及 cron
* 表達式
* 來控制任務執行的時間間隔與規則。實際啟用的是 {@code triggerScheduleTask()} 方法,使用 cron 表達式每分鐘執行一次。
*
*
*
* 主要方法說明:
* <ul>
* <li>{@link #triggerScheduleTask()}:每分鐘觸發一次排程任務,並呼叫
* {@link #doScheduleTask(Class)}
* 方法執行任務邏輯。</li>
* <li>{@link #doTaskWorker()}:模擬任務執行流程,包含 10 秒的暫停(sleep),可用於測試排程任務的執行狀態。</li>
* </ul>
*
*
*
* 注意事項:
* <ul>
* <li>本範例不涉及資料庫操作,適合用於純計算或外部 API 呼叫等無需 EntityManager 的排程任務。</li>
* <li>如需調整排程頻率,請修改 @Scheduled 註解的參數。</li>
* </ul>
*
*
* @author Lewis
* @since 2024
*/
@Service
@Slf4j
public class ScheduleTaskWorkerNoEntityManagerSample extends BaseScheduleTask {
/**
* Fix for javadoc warning :
* use of default constructor, which does not provide a comment
* Constructs a new ScheduleTaskWorkerNoEntityManagerSample instance.
* This is the default constructor, implicitly provided by the compiler
* if no other constructors are defined.
*/
public ScheduleTaskWorkerNoEntityManagerSample() {
// Constructor body (can be empty)
}
// 自行加入 @Scheduled 註解
/**
*
* triggerScheduleTask.
*
*/
@Scheduled(cron = "0 0/1 * * * ?") // 每1分鐘執行
public void triggerScheduleTask() {
// Class for Schedule Task
Class<?> className = this.getClass();
String scheduleTaskName = className.getSimpleName();
log.info(scheduleTaskName + " has been trigger !");
doScheduleTask(className);
}
/**
*
* doTaskWorker.
*
*/
public void doTaskWorker() {
// taskWorker do work
Class<?> className = this.getClass();
String scheduleTaskName = className.getSimpleName();
log.info("Do TaskWorker ==> " + scheduleTaskName);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("End TaskWorker ==> " + scheduleTaskName);
}
}
** 新增 ScheduleTaskWorkerPrimarySample 範例 於 worker Package
主要展示新增排程任務,
package tw.lewishome.webapp.base.schedule.worker;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import tw.lewishome.webapp.base.schedule.BaseScheduleTask;
import tw.lewishome.webapp.database.primary.entity.SysScheduleLogEntity;
/**
*
* ScheduleTaskWorkerPrimarySample 是一個排程任務的範例實作類別,繼承自
* {@link tw.lewishome.webapp.base.schedule.BaseScheduleTask}。
* 此類別主要用於示範如何在 Spring 框架下,透過 @Scheduled 註解進行定時任務的執行,
* 並且指定使用 primaryEntityManager 進行資料庫操作。
*
*
*
* 主要功能包含:
* <ul>
* <li>透過 @Scheduled(cron = "0 0/1 * * * ?") 設定每分鐘執行一次 triggerScheduleTask
* 方法。</li>
* <li>triggerScheduleTask 方法會觸發排程任務,並呼叫 doScheduleTask 進行任務處理。</li>
* <li>doTaskWorker 方法為實際執行任務的範例,包含模擬任務執行時間(休眠 10 秒),可依需求進行資料庫查詢或其他業務邏輯。</li>
* <li>支援多種 @Scheduled
* 設定方式(fixedDelay、fixedRate、initialDelay、cron),可依排程需求調整。</li>
* <li>自動注入 primaryEntityManager,方便進行 JPA 資料庫操作。</li>
* </ul>
*
*
*
* 使用說明:
* <ul>
* <li>將此類別註冊為 Spring 的 Service。</li>
* <li>可依需求調整 @Scheduled 註解的排程規則。</li>
* <li>如需查詢資料庫,可透過 entityManager 執行 JPQL 查詢。</li>
* </ul>
*
*
*
* 注意事項:
* <ul>
* <li>排程任務執行時,請注意資源管理與例外處理,避免長時間阻塞或資源洩漏。</li>
* <li>如有多個 EntityManager,請確認 @Qualifier 設定正確。</li>
* </ul>
*
*
* @author Lewis
* @since 2024
*/
@Service
@Slf4j
public class ScheduleTaskWorkerPrimarySample extends BaseScheduleTask {
/**
* Fix for javadoc warning :
* use of default constructor, which does not provide a comment
* Constructs a new ScheduleTaskWorkerPrimarySample instance.
* This is the default constructor, implicitly provided by the compiler
* if no other constructors are defined.
*/
public ScheduleTaskWorkerPrimarySample() {
// Constructor body (can be empty)
}
/**
* 指定使用 primaryEntityManager
*/
@Autowired // 不指定@Qualifier 自動使用 primaryEntityManagerFactory
private @Qualifier("primaryEntityManager") EntityManager entityManager;
// @Scheduled的設定方案:
// @Scheduled(fixedDelay = 5000) // fixedDelay = 5000表示當前方法執行完畢5000ms後,Spring
// scheduling會再次呼叫該方法
// public void testFixDelay() {
// logger.info("===fixedDelay: 第{}次執行方法", fixedDelayCount);
// }
//
// @Scheduled(fixedRate = 5000) // fixedRate = 5000表示當前方法開始執行5000ms後,Spring
// scheduling會再次呼叫該方法
// public void testFixedRate() {
// logger.info("===fixedRate: 第{}次執行方法", fixedRateCount);
// }
//
// @Scheduled(initialDelay = 1000, fixedRate = 5000) // initialDelay =
// 1000表示延遲1000ms執行第一次任務
// public void testInitialDelay() {
// logger.info("===initialDelay: 第{}次執行方法", initialDelayCount);
// }
// cron 接受cron 表示式,根據cron表示式確定定時規則
// @Scheduled(cron = "0 0/10 * * * ?") // 每10分鐘
// "second, minute, hour, day of month, month, day(s) of week"
// public void testCron() {
// logger.info("===Crob: 第{}次執行方法", Cron);
// }
/**
*
* triggerScheduleTask.
*
*/
@Scheduled(cron = "0 0/1 * * * ?") // 每1分鐘執行
public void triggerScheduleTask() {
// Class for Schedule Task
Class<?> className = this.getClass();
String scheduleTaskName = className.getSimpleName();
log.info(scheduleTaskName + " has been trigger !");
doScheduleTask(className);
}
/**
*
* doTaskWorker.
*
*/
// @SuppressWarnings("unchecked")
@SuppressWarnings("unchecked")
public void doTaskWorker() {
// taskworker do work
Class<?> className = this.getClass();
String scheduleTaskName = className.getSimpleName();
log.info("Do TaskWorker ==> " + scheduleTaskName);
try {
String hql = "SELECT u from SysScheduleLogEntity u ";
List<SysScheduleLogEntity> results = entityManager.createQuery(hql).getResultList();
results.forEach(x -> {
log.info(x.toString());
});
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
log.info("End TaskWorker ==> " + scheduleTaskName);
}
}
** console log畫面

** SysScheduleCtlEntity資料

** SysScheduleLogEntity資料
